import { DOCUMENT } from '@angular/common';
import { isNull } from '@angular/compiler/src/output/output_ast';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, Inject, Input, OnDestroy, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { getElementOffset } from 'ng-zorro-antd/core/util';
import { fromEvent, merge, Observable, Subscription } from 'rxjs';
import { distinctUntilChanged, filter, map, pluck, takeUntil, tap } from 'rxjs/internal/operators';
import { isArray } from 'src/app/utils/array';
import { getPercent, limitNumberInRange } from 'src/app/utils/number';
import { sliderEvent } from './wy-slider-helper';
import { SliderEventObserverConfig, SliderValue } from './wy-slider-types';

@Component({
  selector: 'app-wy-slider',
  templateUrl: './wy-slider.component.html',
  styleUrls: ['./wy-slider.component.less'],
  encapsulation:ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers:[{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(()=>WySliderComponent),
    multi: true
  }]
})
export class WySliderComponent implements OnInit, OnDestroy, ControlValueAccessor {
  @Input() wyVertical = false;
  @Input() wyMin = 0;
  @Input() wyMax = 100;
  @Input() bufferOffset:SliderValue = 0;

  @Output() wyOnAfterChange = new EventEmitter<SliderValue>()

  private sliderDom!: HTMLDivElement;
  @ViewChild("wySlider",{static:true}) private wySlider!:ElementRef;
  private dragStart$!: Observable<number>;
  private dragMove$!: Observable<number>;
  private dragEnd$!: Observable<Event>;

  private dragStart_!: Subscription | null;
  private dragMove_!: Subscription | null;
  private dragEnd_!: Subscription | null;


  private isDragging = false;

  value: SliderValue = null;
  offset: SliderValue = null;


  constructor(@Inject(DOCUMENT) private doc:Document,private cdr: ChangeDetectorRef) { }



  ngOnInit(): void {
    this.sliderDom = this.wySlider.nativeElement
    this.createDraggingObservables()
    this.subscribeDrag(['start'])
  }
  private createDraggingObservables(){
    const orientField = this.wyVertical ? 'pageY':"pageX"
    //PC滑动事件
    const mouse:any = {
      start: "mousedown",
      move: "mousemove",
      end: "mouseup",
      filter:(e:MouseEvent) => e instanceof MouseEvent,
      pluckkey:[orientField], //获得鼠标按下位置
    };

    //移动滑动事件
    const touch:any = {
      start: "touchstart",
      move: "touchmove",
      end: "touchend",
      filter:(e:TouchEvent) => e instanceof TouchEvent,
      pluckkey:["touches","0",orientField], //获得鼠标按下位置
    };
    //滑动事件绑定
    [mouse,touch].forEach((source:any) => {
      const {start,move,end,filter:filterFunc,pluckkey} = source;
      source.startPlucked$ = fromEvent(this.sliderDom,start)
      .pipe(
        filter(filterFunc),
        tap(sliderEvent),
        pluck(pluckkey),
        map((position:number) => this.findClosestValue(position))
      );
      //结束事件
      source.end$ = fromEvent(this.doc,end);

      source.moveResolved$ = fromEvent(this.doc,move)
      .pipe(
        filter(filterFunc),
        tap(sliderEvent),
        pluck(pluckkey),
        distinctUntilChanged(),
        map((position:number) => this.findClosestValue(position)),
        takeUntil(source.end$)
      );;
    });
    this.dragStart$ = merge(mouse.startPlucked$,touch.startPlucked$);
    this.dragMove$ = merge(mouse.moveResolved$,touch.moveResolved$);
    this.dragEnd$ = merge(mouse.end$,touch.end$);
  }
  //订阅滑块事件
  private subscribeDrag(events:string[] = ['start','move','end']){
    if(isArray(events,'start') && this.dragStart$ && !this.dragStart_){
      this.dragStart_ = this.dragStart$.subscribe(this.onDragStart.bind(this))
    }
    if(isArray(events,'move') && this.dragMove$ && !this.dragMove_){
      this.dragMove_ = this.dragMove$.subscribe(this.onDragMove.bind(this))
    }
    if(isArray(events,'end') && this.dragEnd$ && !this.dragEnd_){
      this.dragEnd_ = this.dragEnd$.subscribe(this.onDragEnd.bind(this))
    }
  }
  //解绑滑块事件
  private unsubscribeDrag(events:string[] = ['start','move','end']){
    if(isArray(events,'start') && this.dragStart_){
      this.dragStart_.unsubscribe();
      this.dragStart_ = null;
    }
    if(isArray(events,'move') && this.dragMove_){
      this.dragMove_.unsubscribe();
      this.dragMove_ = null;
    }
    if(isArray(events,'end') && this.dragEnd_){
      this.dragEnd_.unsubscribe();
      this.dragEnd_ = null;
    }
  }
  private onDragStart(value:number){
    this.toggleDragMoving(true);
    this.setValuer(value);
  }
  private onDragMove(value:number){
    if(this.isDragging){
      this.setValuer(value);
      this.cdr.markForCheck()
    }
  }
  private onDragEnd(){
    this.wyOnAfterChange.emit(this.value)
    this.toggleDragMoving(false);
    this.cdr.markForCheck()
  }

  private setValuer(value: SliderValue,needCheck = false){
    if(needCheck){
      if(this.isDragging) return;
      this.value = this.formatValue(value)
      this.updateTrackAndHandles()
    }else if(!this.valueEqual(this.value,value)){
      this.value = value
      this.updateTrackAndHandles()
      this.onValueChange(this.value)
    }
  }

  private formatValue(value:SliderValue):SliderValue{
    let res = value
    if(this.assertValuerValid(value)){
      res = this.wyMin
    }else{
      res = limitNumberInRange((value || 0),this.wyMin,this.wyMax)
    }
    return res
  }

  //判断是否是NAM
  private assertValuerValid(value:SliderValue):boolean{
    if(value === null){
      return isNaN(typeof value !== 'number' ? parseFloat('null'):value)
    }
    return isNaN(typeof value !== 'number' ? parseFloat(value):value)
  }

  private valueEqual(valA:SliderValue,valB:SliderValue):boolean{
    if(typeof valA !== typeof valB){
      return false;
    }
    return valA === valB
  }

  private updateTrackAndHandles(){
    this.offset = this.getValuerToOffset(this.value)
    this.cdr.markForCheck()
  }

  private getValuerToOffset(value:SliderValue):SliderValue{
    const valueData = value ? value: 0
    return getPercent(valueData,this.wyMin,this.wyMax);
  }


  //绑定和解绑事件
  toggleDragMoving(movable:boolean){
    if(movable){
      this.isDragging = movable
      this.subscribeDrag(['move','end'])
    }else{
      this.isDragging = false
      this.unsubscribeDrag(['move','end'])
    }
  }


  private findClosestValue (position: number){
    // 获得滑块总长
    const solderLength = this.getSliderLength()
    //获得(左,上)滑块端点位置
    const sliderStart = this.getSliderStartPosition();

    //滑块当前位置 / 滑块总长
    const ratio = limitNumberInRange((position - sliderStart) / solderLength,0,1);
    const reaioTrue = this.wyVertical ? 1-ratio:ratio;
    return reaioTrue * (this.wyMax - this.wyMin) + this.wyMin
  }

  private getSliderLength() :number{
    return this.wyVertical ? this.sliderDom.clientHeight : this.sliderDom.clientWidth
  }

  private getSliderStartPosition() :number{
    const offset = getElementOffset(this.sliderDom)
    return this.wyVertical ? offset.top : offset.left;
  }
  private onValueChange(value:SliderValue):void{

  }
  private onTouched():void{

  }
  writeValue(value: SliderValue): void {
    this.setValuer(value,true)
  }
  registerOnChange(fn: (value: SliderValue)=>void): void {
    this.onValueChange = fn
  }
  registerOnTouched(fn: ()=>void): void {
    this.onTouched = fn
  }
  ngOnDestroy(): void {
    this.unsubscribeDrag()
  }
}
